﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Text.RegularExpressions;
using Common.Utilities;

namespace Workflows.Components.Entities.Addresses
{
    /// <summary>
    /// new line and then comma separated 
    /// lookup country from dictionary (including abbrev.) 
    /// for US. lookup state if available 
    /// for US, lookup city if available 
    /// zip code \d{5}|\d{5}-\d{4}, invalid if before city/state 
    /// street address (ln, ct, rd, st, etc) or PO box or apt -- key word match 
    /// Dept/Inst/Laboratory (within Company/School) -- key word match 
    /// college/university/company -- key word match
    /// </summary>
    public class AddressParser
    {
        private string baseResourceName = "Workflows.Components.Entities.Resources";

        private Dictionary<string,List<string>> _USCities;
        /// <summary>city name --> list of states, 2 letter state</summary>
        public Dictionary<string, List<string>> USCities
        {
            get
            {
                return this._USCities;
            }
            set
            {
                this._USCities = value;
            }
        }

        private Dictionary<string,string> _USStates;
        /// <summary>2-letter -> full name</summary>
        public Dictionary<string,string> USStates
        {
            get
            {
                return this._USStates;
            }
            set
            {
                this._USStates = value;
            }
        }

        private Dictionary<string,string> _USZipCodes;
        /// <summary>5-digit zip code -> state</summary>
        public Dictionary<string,string> USZipCodes
        {
            get
            {
                return this._USZipCodes;
            }
            set
            {
                this._USZipCodes = value;
            }
        }

        private Dictionary<string,string> _Countries;
        /// <summary>country name -> 2-letter code</summary>
        public Dictionary<string,string> Countries
        {
            get
            {
                return this._Countries;
            }
            set
            {
                this._Countries = value;
            }
        }

        private List<string> _StreetKeyWords;
        private List<string> _DeptKeyWords;
        private List<string> _CompanyKeyWords;
        private List<string> _CountryKeyWords;

        public AddressParser()
        {
            this.PopulateStateAbbrev();
            this.ParsePlaces();
            this.ParseZipCodes();
            this.PopulateCountries();
            this.PopulateKeywords();
        }

        private void PopulateStateAbbrev()
        {
            this._USStates = new Dictionary<string, string>();
            this._USStates.Add("ALABAMA", "AL");
            this._USStates.Add("ALASKA", "AK");
            this._USStates.Add("AMERICAN SAMOA", "AS");
            this._USStates.Add("ARIZONA", "AZ");
            this._USStates.Add("ARKANSAS", "AR");
            this._USStates.Add("CALIFORNIA", "CA");
            this._USStates.Add("COLORADO", "CO");
            this._USStates.Add("CONNECTICUT", "CT");
            this._USStates.Add("DELAWARE", "DE");
            this._USStates.Add("DISTRICT OF COLUMBIA", "DC");
            this._USStates.Add("FEDERATED STATES OF MICRONESIA", "FM");
            this._USStates.Add("FLORIDA", "FL");
            this._USStates.Add("GEORGIA", "GA");
            this._USStates.Add("GUAM", "GU");
            this._USStates.Add("HAWAII", "HI");
            this._USStates.Add("IDAHO", "ID");
            this._USStates.Add("ILLINOIS", "IL");
            this._USStates.Add("INDIANA", "IN");
            this._USStates.Add("IOWA", "IA");
            this._USStates.Add("KANSAS", "KS");
            this._USStates.Add("KENTUCKY", "KY");
            this._USStates.Add("LOUISIANA", "LA");
            this._USStates.Add("MAINE", "ME");
            this._USStates.Add("MARSHALL ISLANDS", "MH");
            this._USStates.Add("MARYLAND", "MD");
            this._USStates.Add("MASSACHUSETTS", "MA");
            this._USStates.Add("MICHIGAN", "MI");
            this._USStates.Add("MINNESOTA", "MN");
            this._USStates.Add("MISSISSIPPI", "MS");
            this._USStates.Add("MISSOURI", "MO");
            this._USStates.Add("MONTANA", "MT");
            this._USStates.Add("NEBRASKA", "NE");
            this._USStates.Add("NEVADA", "NV");
            this._USStates.Add("NEW HAMPSHIRE", "NH");
            this._USStates.Add("NEW JERSEY", "NJ");
            this._USStates.Add("NEW MEXICO", "NM");
            this._USStates.Add("NEW YORK", "NY");
            this._USStates.Add("NORTH CAROLINA", "NC");
            this._USStates.Add("NORTH DAKOTA", "ND");
            this._USStates.Add("NORTHERN MARIANA ISLANDS", "MP");
            this._USStates.Add("OHIO", "OH");
            this._USStates.Add("OKLAHOMA", "OK");
            this._USStates.Add("OREGON", "OR");
            this._USStates.Add("PALAU", "PW");
            this._USStates.Add("PENNSYLVANIA", "PA");
            this._USStates.Add("PUERTO RICO", "PR");
            this._USStates.Add("RHODE ISLAND", "RI");
            this._USStates.Add("SOUTH CAROLINA", "SC");
            this._USStates.Add("SOUTH DAKOTA", "SD");
            this._USStates.Add("TENNESSEE", "TN");
            this._USStates.Add("TEXAS", "TX");
            this._USStates.Add("UTAH", "UT");
            this._USStates.Add("VERMONT", "VT");
            this._USStates.Add("VIRGIN ISLANDS", "VI");
            this._USStates.Add("VIRGINIA", "VA");
            this._USStates.Add("WASHINGTON", "WA");
            this._USStates.Add("WEST VIRGINIA", "WV");
            this._USStates.Add("WISCONSIN", "WI");
            this._USStates.Add("WYOMING", "WY");
        }

        private void ParsePlaces()
        {
            this._USCities = new Dictionary<string, List<string>>();
            
            StreamReader streamReader = ResourceUtil.GetEmbeddedFileContent(this.GetType().Assembly, baseResourceName + ".places2k.txt");
            if (streamReader != null)
            {
                while (!streamReader.EndOfStream)
                {
                    string line = streamReader.ReadLine();
                    string stateAbbrev = line.Substring(0, 2).ToUpper();
                    string cityName = line.Substring(9, 64).Trim().ToUpper();
                    string[] tokens = StringUtil.Split(cityName, " ");
                    if(tokens.Length>1)
                    {
                        cityName = string.Empty;
                        for(int i=0;i<tokens.Length-1;i++)
                        {
                            cityName += tokens[i];
                            if(i<tokens.Length -2)
                            {
                                cityName += " ";
                            }
                        }
                    }
                    if(this._USCities.ContainsKey(cityName))
                    {
                        List<string> states = this._USCities[cityName];
                        if(!states.Contains(stateAbbrev))
                        {
                            states.Add(stateAbbrev);
                            this._USCities[cityName] = states;
                        }
                    }
                    else
                    {
                        List<string> states = new List<string>();
                        states.Add(stateAbbrev);
                        this._USCities.Add(cityName, states);
                    }
                }
                streamReader.Close();
            }
        }

        private void ParseZipCodes()
        {
            this._USZipCodes = new Dictionary<string, string>();
            StreamReader streamReader = ResourceUtil.GetEmbeddedFileContent(this.GetType().Assembly, baseResourceName + ".zcta5.txt");
            if (streamReader != null)
            {
                while (!streamReader.EndOfStream)
                {
                    string line = streamReader.ReadLine();
                    string state = line.Substring(0, 2).ToUpper();
                    string zipCode = line.Substring(2, 5);
                    if(!this._USZipCodes.ContainsKey(zipCode))
                    {
                        this._USZipCodes.Add(zipCode, state);
                    }
                }
                streamReader.Close();
            }
        }

        private void PopulateCountries()
        {
            this._Countries = new Dictionary<string, string>();
            this._Countries.Add("Andorra", "AD");
            this._Countries.Add("United Arab Emirates", "AE");
            this._Countries.Add("Afghanistan", "AF");
            this._Countries.Add("Antigua & Barbuda", "AG");
            this._Countries.Add("Anguilla", "AI");
            this._Countries.Add("Albania", "AL");
            this._Countries.Add("Armenia", "AM");
            this._Countries.Add("Netherlands Antilles", "AN");
            this._Countries.Add("Angola", "AO");
            this._Countries.Add("Antarctica", "AQ");
            this._Countries.Add("Argentina", "AR");
            this._Countries.Add("American Samoa", "AS");
            this._Countries.Add("Austria", "AT");
            this._Countries.Add("Australia", "AU");
            this._Countries.Add("Aruba", "AW");
            this._Countries.Add("Azerbaijan", "AZ");
            this._Countries.Add("Bosnia and Herzegovina", "BA");
            this._Countries.Add("Barbados", "BB");
            this._Countries.Add("Bangladesh", "BD");
            this._Countries.Add("Belgium", "BE");
            this._Countries.Add("Burkina Faso", "BF");
            this._Countries.Add("Bulgaria", "BG");
            this._Countries.Add("Bahrain", "BH");
            this._Countries.Add("Burundi", "BI");
            this._Countries.Add("Benin", "BJ");
            this._Countries.Add("Bermuda", "BM");
            this._Countries.Add("Brunei Darussalam", "BN");
            this._Countries.Add("Bolivia", "BO");
            this._Countries.Add("Brazil", "BR");
            this._Countries.Add("Bahama", "BS");
            this._Countries.Add("Bhutan", "BT");
            this._Countries.Add("Burma (no longer exists)", "BU");
            this._Countries.Add("Bouvet Island", "BV");
            this._Countries.Add("Botswana", "BW");
            this._Countries.Add("Belarus", "BY");
            this._Countries.Add("Belize", "BZ");
            this._Countries.Add("Canada", "CA");
            this._Countries.Add("Cocos (Keeling) Islands", "CC");
            this._Countries.Add("Central African Republic", "CF");
            this._Countries.Add("Congo", "CG");
            this._Countries.Add("Switzerland", "CH");
            this._Countries.Add("Côte D'ivoire (Ivory Coast)", "CI");
            this._Countries.Add("Cook Iislands", "CK");
            this._Countries.Add("Chile", "CL");
            this._Countries.Add("Cameroon", "CM");
            this._Countries.Add("China", "CN");
            this._Countries.Add("Colombia", "CO");
            this._Countries.Add("Costa Rica", "CR");
            this._Countries.Add("Czechoslovakia (no longer exists)", "CS");
            this._Countries.Add("Cuba", "CU");
            this._Countries.Add("Cape Verde", "CV");
            this._Countries.Add("Christmas Island", "CX");
            this._Countries.Add("Cyprus", "CY");
            this._Countries.Add("Czech Republic", "CZ");
            this._Countries.Add("German Democratic Republic (no longer exists)", "DD");
            this._Countries.Add("Germany", "DE");
            this._Countries.Add("Djibouti", "DJ");
            this._Countries.Add("Denmark", "DK");
            this._Countries.Add("Dominica", "DM");
            this._Countries.Add("Dominican Republic", "DO");
            this._Countries.Add("Algeria", "DZ");
            this._Countries.Add("Ecuador", "EC");
            this._Countries.Add("Estonia", "EE");
            this._Countries.Add("Egypt", "EG");
            this._Countries.Add("Western Sahara", "EH");
            this._Countries.Add("Eritrea", "ER");
            this._Countries.Add("Spain", "ES");
            this._Countries.Add("Ethiopia", "ET");
            this._Countries.Add("Finland", "FI");
            this._Countries.Add("Fiji", "FJ");
            this._Countries.Add("Falkland Islands (Malvinas)", "FK");
            this._Countries.Add("Micronesia", "FM");
            this._Countries.Add("Faroe Islands", "FO");
            this._Countries.Add("France", "FR");
            this._Countries.Add("France, Metropolitan", "FX");
            this._Countries.Add("Gabon", "GA");
            this._Countries.Add("United Kingdom (Great Britain)", "GB");
            this._Countries.Add("Great Britain", "GB");
            this._Countries.Add("United Kingdom", "GB");
            this._Countries.Add("UK", "GB");
            this._Countries.Add("U.K.", "GB");
            this._Countries.Add("Grenada", "GD");
            this._Countries.Add("Georgia", "GE");
            this._Countries.Add("French Guiana", "GF");
            this._Countries.Add("Ghana", "GH");
            this._Countries.Add("Gibraltar", "GI");
            this._Countries.Add("Greenland", "GL");
            this._Countries.Add("Gambia", "GM");
            this._Countries.Add("Guinea", "GN");
            this._Countries.Add("Guadeloupe", "GP");
            this._Countries.Add("Equatorial Guinea", "GQ");
            this._Countries.Add("Greece", "GR");
            this._Countries.Add("South Georgia and the South Sandwich Islands", "GS");
            this._Countries.Add("Guatemala", "GT");
            this._Countries.Add("Guam", "GU");
            this._Countries.Add("Guinea-Bissau", "GW");
            this._Countries.Add("Guyana", "GY");
            this._Countries.Add("Hong Kong", "HK");
            this._Countries.Add("Heard & McDonald Islands", "HM");
            this._Countries.Add("Honduras", "HN");
            this._Countries.Add("Croatia", "HR");
            this._Countries.Add("Haiti", "HT");
            this._Countries.Add("Hungary", "HU");
            this._Countries.Add("Indonesia", "ID");
            this._Countries.Add("Ireland", "IE");
            this._Countries.Add("Israel", "IL");
            this._Countries.Add("India", "IN");
            this._Countries.Add("British Indian Ocean Territory", "IO");
            this._Countries.Add("Iraq", "IQ");
            this._Countries.Add("Islamic Republic of Iran", "IR");
            this._Countries.Add("Iceland", "IS");
            this._Countries.Add("Italy", "IT");
            this._Countries.Add("Jamaica", "JM");
            this._Countries.Add("Jordan", "JO");
            this._Countries.Add("Japan", "JP");
            this._Countries.Add("Kenya", "KE");
            this._Countries.Add("Kyrgyzstan", "KG");
            this._Countries.Add("Cambodia", "KH");
            this._Countries.Add("Kiribati", "KI");
            this._Countries.Add("Comoros", "KM");
            this._Countries.Add("St. Kitts and Nevis", "KN");
            this._Countries.Add("Korea, Democratic People's Republic of", "KP");
            this._Countries.Add("Korea, Republic of", "KR");
            this._Countries.Add("Kuwait", "KW");
            this._Countries.Add("Cayman Islands", "KY");
            this._Countries.Add("Kazakhstan", "KZ");
            this._Countries.Add("Lao People's Democratic Republic", "LA");
            this._Countries.Add("Lebanon", "LB");
            this._Countries.Add("Saint Lucia", "LC");
            this._Countries.Add("Liechtenstein", "LI");
            this._Countries.Add("Sri Lanka", "LK");
            this._Countries.Add("Liberia", "LR");
            this._Countries.Add("Lesotho", "LS");
            this._Countries.Add("Lithuania", "LT");
            this._Countries.Add("Luxembourg", "LU");
            this._Countries.Add("Latvia", "LV");
            this._Countries.Add("Libyan Arab Jamahiriya", "LY");
            this._Countries.Add("Morocco", "MA");
            this._Countries.Add("Monaco", "MC");
            this._Countries.Add("Moldova, Republic of", "MD");
            this._Countries.Add("Madagascar", "MG");
            this._Countries.Add("Marshall Islands", "MH");
            this._Countries.Add("Mali", "ML");
            this._Countries.Add("Mongolia", "MN");
            this._Countries.Add("Myanmar", "MM");
            this._Countries.Add("Macau", "MO");
            this._Countries.Add("Northern Mariana Islands", "MP");
            this._Countries.Add("Martinique", "MQ");
            this._Countries.Add("Mauritania", "MR");
            this._Countries.Add("Monserrat", "MS");
            this._Countries.Add("Malta", "MT");
            this._Countries.Add("Mauritius", "MU");
            this._Countries.Add("Maldives", "MV");
            this._Countries.Add("Malawi", "MW");
            this._Countries.Add("Mexico", "MX");
            this._Countries.Add("Malaysia", "MY");
            this._Countries.Add("Mozambique", "MZ");
            this._Countries.Add("Namibia", "NA");
            this._Countries.Add("New Caledonia", "NC");
            this._Countries.Add("Niger", "NE");
            this._Countries.Add("Norfolk Island", "NF");
            this._Countries.Add("Nigeria", "NG");
            this._Countries.Add("Nicaragua", "NI");
            this._Countries.Add("Netherlands", "NL");
            this._Countries.Add("Norway", "NO");
            this._Countries.Add("Nepal", "NP");
            this._Countries.Add("Nauru", "NR");
            this._Countries.Add("Neutral Zone (no longer exists)", "NT");
            this._Countries.Add("Niue", "NU");
            this._Countries.Add("New Zealand", "NZ");
            this._Countries.Add("Oman", "OM");
            this._Countries.Add("Panama", "PA");
            this._Countries.Add("Peru", "PE");
            this._Countries.Add("French Polynesia", "PF");
            this._Countries.Add("Papua New Guinea", "PG");
            this._Countries.Add("Philippines", "PH");
            this._Countries.Add("Pakistan", "PK");
            this._Countries.Add("Poland", "PL");
            this._Countries.Add("St. Pierre & Miquelon", "PM");
            this._Countries.Add("Pitcairn", "PN");
            this._Countries.Add("Puerto Rico", "PR");
            this._Countries.Add("Portugal", "PT");
            this._Countries.Add("Palau", "PW");
            this._Countries.Add("Paraguay", "PY");
            this._Countries.Add("Qatar", "QA");
            this._Countries.Add("Réunion", "RE");
            this._Countries.Add("Romania", "RO");
            this._Countries.Add("Russian Federation", "RU");
            this._Countries.Add("Rwanda", "RW");
            this._Countries.Add("Saudi Arabia", "SA");
            this._Countries.Add("Solomon Islands", "SB");
            this._Countries.Add("Seychelles", "SC");
            this._Countries.Add("Sudan", "SD");
            this._Countries.Add("Sweden", "SE");
            this._Countries.Add("Singapore", "SG");
            this._Countries.Add("St. Helena", "SH");
            this._Countries.Add("Slovenia", "SI");
            this._Countries.Add("Svalbard & Jan Mayen Islands", "SJ");
            this._Countries.Add("Slovakia", "SK");
            this._Countries.Add("Sierra Leone", "SL");
            this._Countries.Add("San Marino", "SM");
            this._Countries.Add("Senegal", "SN");
            this._Countries.Add("Somalia", "SO");
            this._Countries.Add("Suriname", "SR");
            this._Countries.Add("Sao Tome & Principe", "ST");
            this._Countries.Add("Union of Soviet Socialist Republics (no longer exists)", "SU");
            this._Countries.Add("El Salvador", "SV");
            this._Countries.Add("Syrian Arab Republic", "SY");
            this._Countries.Add("Swaziland", "SZ");
            this._Countries.Add("Turks & Caicos Islands", "TC");
            this._Countries.Add("Chad", "TD");
            this._Countries.Add("French Southern Territories", "TF");
            this._Countries.Add("Togo", "TG");
            this._Countries.Add("Thailand", "TH");
            this._Countries.Add("Tajikistan", "TJ");
            this._Countries.Add("Tokelau", "TK");
            this._Countries.Add("Turkmenistan", "TM");
            this._Countries.Add("Tunisia", "TN");
            this._Countries.Add("Tonga", "TO");
            this._Countries.Add("East Timor", "TP");
            this._Countries.Add("Turkey", "TR");
            this._Countries.Add("Trinidad & Tobago", "TT");
            this._Countries.Add("Tuvalu", "TV");
            this._Countries.Add("Taiwan, Province of China", "TW");
            this._Countries.Add("Tanzania, United Republic of", "TZ");
            this._Countries.Add("Ukraine", "UA");
            this._Countries.Add("Uganda", "UG");
            this._Countries.Add("United States Minor Outlying Islands", "UM");
            this._Countries.Add("United States of America", "US");
            this._Countries.Add("USA", "US");
            this._Countries.Add("U.S.A.", "US");
            this._Countries.Add("United States", "US");
            this._Countries.Add("Uruguay", "UY");
            this._Countries.Add("Uzbekistan", "UZ");
            this._Countries.Add("Vatican City State (Holy See)", "VA");
            this._Countries.Add("St. Vincent & the Grenadines", "VC");
            this._Countries.Add("Venezuela", "VE");
            this._Countries.Add("British Virgin Islands", "VG");
            this._Countries.Add("United States Virgin Islands", "VI");
            this._Countries.Add("Viet Nam", "VN");
            this._Countries.Add("Vanuatu", "VU");
            this._Countries.Add("Wallis & Futuna Islands", "WF");
            this._Countries.Add("Samoa", "WS");
            this._Countries.Add("Democratic Yemen (no longer exists)", "YD");
            this._Countries.Add("Yemen", "YE");
            this._Countries.Add("Mayotte", "YT");
            this._Countries.Add("Yugoslavia", "YU");
            this._Countries.Add("South Africa", "ZA");
            this._Countries.Add("Zambia", "ZM");
            this._Countries.Add("Zaire", "ZR");
            this._Countries.Add("Zimbabwe", "ZW");
        }

        private void PopulateKeywords()
        {
            this._StreetKeyWords = new List<string>();
            this._StreetKeyWords.Add("ST");
            this._StreetKeyWords.Add("STREET");
            this._StreetKeyWords.Add("COURT");
            this._StreetKeyWords.Add("CT");
            this._StreetKeyWords.Add("ROAD");
            this._StreetKeyWords.Add("RD");
            this._StreetKeyWords.Add("LANE");
            this._StreetKeyWords.Add("LN");
            this._StreetKeyWords.Add("AVENUE");
            this._StreetKeyWords.Add("AVE");
            this._StreetKeyWords.Add("APARTMENT");
            this._StreetKeyWords.Add("APT");

            this._DeptKeyWords = new List<string>();
            this._DeptKeyWords.Add("DEPARTMENT");
            this._DeptKeyWords.Add("DEPT");
            this._DeptKeyWords.Add("LABORATORY");
            this._DeptKeyWords.Add("LAB");
            this._DeptKeyWords.Add("DIVISION");
            this._DeptKeyWords.Add("SHOOL");
            this._DeptKeyWords.Add("INSTITUTE");
            this._DeptKeyWords.Add("INST");

            this._CompanyKeyWords = new List<string>();
            this._CompanyKeyWords.Add("CO.");
            this._CompanyKeyWords.Add("LTD.");
            this._CompanyKeyWords.Add("UNIVERSITY");
            this._CompanyKeyWords.Add("UNIV");
            this._CompanyKeyWords.Add("SCHOOL");
            this._CompanyKeyWords.Add("COLLEGE");

            this._CountryKeyWords = new List<string>();
            foreach(string countryName in this._Countries.Keys)
            {
                this._CountryKeyWords.Add(countryName.ToUpper());
            }
        }

        public Address ParseAddress(int addrId, string wholeAddr)
        {
            Address addr = new Address(addrId);
            // # 1: country
            wholeAddr = wholeAddr.ToUpper();
            foreach(string countryName in this._CountryKeyWords)
            {
                string countryPattern = string.Format("\\b{0}\\b|\\b{0}$", countryName.Replace(".", "\\."));
                if(Regex.IsMatch(wholeAddr, countryPattern))
                {
                    MatchCollection matches = Regex.Matches(wholeAddr, countryPattern);
                    Match lastMatch = matches[matches.Count - 1];
                    addr.Country = lastMatch.Value;
                    wholeAddr =
                        wholeAddr.Substring(0, lastMatch.Index) +
                        wholeAddr.Substring(lastMatch.Index + lastMatch.Value.Length);
                    break;
                }
            }

            if (string.IsNullOrEmpty(addr.Country) || 
                addr.Country == "United States of America" ||
                addr.Country=="USA")
            {
                // # 2 state 
                Range<int> statePos = new Range<int>();
                statePos.FromValue = -1;
                statePos.ToValue = 0;
                foreach(string state in this._USStates.Keys)
                {
                    string statePattern1 = string.Format("\\b{0}\\b|\\b{0}$", state);
                    if(Regex.IsMatch(wholeAddr, statePattern1))
                    {
                        MatchCollection matches = Regex.Matches(wholeAddr, statePattern1);
                        Match lastMatch = matches[matches.Count - 1];
                        if(lastMatch.Index>statePos.FromValue)
                        {
                            statePos.FromValue = lastMatch.Index;
                            statePos.ToValue = lastMatch.Index + lastMatch.Value.Length;
                        }
                        
                    }
                    string statePattern2 = string.Format("\\b{0}\\b|\\b{0}$", this._USStates[state]);
                    if (Regex.IsMatch(wholeAddr, statePattern2))
                    {
                        MatchCollection matches = Regex.Matches(wholeAddr, statePattern2);
                        Match lastMatch = matches[matches.Count - 1];
                        if (lastMatch.Index > statePos.FromValue)
                        {
                            statePos.FromValue = lastMatch.Index;
                            statePos.ToValue = lastMatch.Index + lastMatch.Value.Length;
                        }
                        
                    }

                    if(statePos.FromValue>=0)
                    {
                        addr.Region = wholeAddr.Substring(statePos.FromValue, statePos.ToValue - statePos.FromValue);
                        if (string.IsNullOrEmpty(addr.Country))
                            addr.Country = "USA";
                        //wholeAddr =
                        //    wholeAddr.Substring(0, statePos.FromValue) +
                        //    wholeAddr.Substring(statePos.ToValue);
                        break;
                    }
                }

                // # 3 city 
                Range<int> cityPos = new Range<int>();
                cityPos.FromValue = -1;
                cityPos.ToValue = 0;
                foreach(string city in this._USCities.Keys)
                {
                    string cityPattern = string.Format("\\b{0}\\b", city.Replace(".", "\\."));
                    if (Regex.IsMatch(wholeAddr, cityPattern))
                    {
                        MatchCollection matches = Regex.Matches(wholeAddr, cityPattern);
                        Match lastMatch = matches[matches.Count - 1];
                        if(lastMatch.Index>cityPos.FromValue)
                        {
                            cityPos.FromValue = lastMatch.Index;
                            cityPos.ToValue = lastMatch.Index + lastMatch.Value.Length;
                        }
                        
                    }
                }
                if(cityPos.FromValue>=0)
                {
                    addr.City = wholeAddr.Substring(cityPos.FromValue, cityPos.ToValue - cityPos.FromValue);
                    if (this._USCities.ContainsKey(addr.City) &&
                        this._USCities[addr.City].Count == 1 && 
                        string.IsNullOrEmpty(addr.Region))
                    {
                        addr.Region = this._USCities[addr.City][0];
                    }
                    if (string.IsNullOrEmpty(addr.Country))
                        addr.Country = "USA";
                }

                // # 4 postal code
                Range<int> zipPos = new Range<int>();
                zipPos.FromValue = -1;
                zipPos.ToValue = 0;
                foreach(string zipCode in this._USZipCodes.Keys)
                {
                    string postalCodePattern = string.Format("\\b{0}\\b|\\b{0}$", zipCode);
                    if (Regex.IsMatch(wholeAddr, postalCodePattern))
                    {
                        MatchCollection matches = Regex.Matches(wholeAddr, postalCodePattern);
                        Match lastMatch = matches[matches.Count - 1];
                        if(lastMatch.Index>zipPos.FromValue)
                        {
                            zipPos.FromValue = lastMatch.Index;
                            zipPos.ToValue = lastMatch.Index + lastMatch.Value.Length;
                        }
                        
                    }
                }
                if(zipPos.FromValue>=0)
                {
                    addr.PostalCode = wholeAddr.Substring(zipPos.FromValue, zipPos.ToValue - zipPos.FromValue);
                    if (string.IsNullOrEmpty(addr.Region) && this._USZipCodes.ContainsKey(addr.PostalCode))
                        addr.Region = this._USZipCodes[addr.PostalCode];
                    if (string.IsNullOrEmpty(addr.Country))
                        addr.Country = "USA";
                }
            }

            string[] lines = wholeAddr.Split(new char[] { ',', '\n' });

            // # 5 org
            foreach(string orgKeyWord in this._CompanyKeyWords)
            {
                string pattern = string.Format("\\b{0}\\b", orgKeyWord.Replace(".", "\\."));
                foreach(string line in lines)
                {
                    if(Regex.IsMatch(line,pattern))
                    {
                        addr.Organization = line;
                    }
                }
            }

            // # 6 dept 
            foreach(string deptKeyword in this._DeptKeyWords)
            {
                string pattern = string.Format("\\b{0}\\b", deptKeyword.Replace(".", "\\."));
                foreach(string line in lines)
                {
                    if (Regex.IsMatch(line, pattern))
                    {
                        if(string.IsNullOrEmpty(addr.Organization) || addr.Organization != line)
                        {
                            addr.SubDivision = line;
                        }
                    }
                }
            }

            // # 7 street 
            addr.Street = string.Empty;
            foreach(string streetKeyword in this._StreetKeyWords)
            {
                string pattern = string.Format("\\b{0}\\b", streetKeyword.Replace(".", "\\."));
                foreach(string line in lines)
                {
                    if (Regex.IsMatch(line, pattern))
                    {
                        bool lineUsed = false;
                        if (!string.IsNullOrEmpty(addr.Organization) && addr.Organization == line)
                            lineUsed = true;
                        if (!string.IsNullOrEmpty(addr.SubDivision) && addr.SubDivision == line)
                            lineUsed = true;

                        if (!lineUsed)
                        {
                            if (addr.Street.Length > 0)
                                addr.Street += ", ";
                            addr.Street = line;
                        }
                    }
                }
            }
            if (addr.Street.Length == 0)
                addr.Street = null;

            addr.FullAddress = wholeAddr;

            return addr;
        }
    }
}
